package exec

import (
	"context"
	"encoding/json"
	"operator/config"
	"operator/controllers"
	"operator/utils/logs"

	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	v1 "k8s.io/api/core/v1"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/tools/remotecommand"
)

/* 前端传到后端
1. container
2. follow
3. tailLines

ws://127.0.0.1:8081/api/pod/logs/?clusterId=cluster101&namespace=default&name=redis-8464b6fbc9-9qt4d&container=redis&defaultCommand=xxxx

*/

// 封装一个消息的结构体
type WsMessage struct {
	// 定义消息类型，执行命令还是调整大小
	MsgType string `json:"msgType"`
	// 表示执行的命令是什么
	Data string `json:"data"`
	// 窗口大小
	Rows uint16 `json:"rows"`
	Cols uint16 `json:"cols"`
}

// 定义一个结构体，实现streamOptions相关接口
type ptyHandler struct {
	ws         *websocket.Conn
	resizeChan chan remotecommand.TerminalSize
}

// 定义Read方法实现io.Reader接口 . 从前端把命令获取到给后端，怎么让后端执行结果返回给前端
func (pty *ptyHandler) Read(p []byte) (int, error) {
	logs.Debug(map[string]interface{}{"data": string(p)}, "执行命令")
	// 1. 获取ws数据
	_, msg, err := pty.ws.ReadMessage()
	if err != nil {
		logs.Error(map[string]interface{}{"msg": err.Error()}, "读取数据失败")
		return 0, err
	}
	// 2. 判断是执行命令还是调整大小
	// 解析消息
	var wsMessage WsMessage
	if err := json.Unmarshal(msg, &wsMessage); err != nil {
		logs.Error(map[string]interface{}{"msg": err.Error()}, "解析消息失败")
		return 0, err
	}
	if wsMessage.MsgType == "resize" {
		logs.Debug(nil, "调整窗口大小")
		var size remotecommand.TerminalSize
		size.Height = wsMessage.Rows
		size.Width = wsMessage.Cols
		// 扔到调整窗口大小的队列
		pty.resizeChan <- size
		return 0, nil
	} else {
		// 执行命令
		logs.Debug(map[string]interface{}{"command": wsMessage.Data}, "执行命令")
		wsDataByte := []byte(wsMessage.Data)
		// copy(dst,src)中。
		n := copy(p, wsDataByte)
		return n, nil
	}
}

// 实现Write io.Writer
func (pty *ptyHandler) Write(p []byte) (int, error) {
	logs.Debug(map[string]interface{}{"data": string(p)}, "命令执行结果")
	returnDataByte := make([]byte, len(p))
	// 使用copy函数将传入的字节切片p中的内容复制到新创建的字节切片returnDataByte中。
	n := copy(returnDataByte, p)
	err := pty.ws.WriteMessage(websocket.TextMessage, returnDataByte)
	return n, err
}

// 实现TerminalSize接口
func (pty *ptyHandler) Next() *remotecommand.TerminalSize {
	size := <-pty.resizeChan
	return &size
}

func Exec(r *gin.Context) {
	logs.Info(nil, "查看Pod日志")
	returnData := config.ReturnData{}
	clientset, basicInfo, _ := controllers.BasicInit(r, nil)

	// if err != nil {
	// 	returnData.Message = err.Error()
	// 	returnData.Status = 400
	// 	r.JSON(200, returnData)
	// 	return
	// }

	if basicInfo.DefaultCommand == "" {
		basicInfo.DefaultCommand = "/bin/bash"
	}

	// 1、创建一个执行命令的request
	execRequest := clientset.CoreV1().RESTClient().Post().Resource("pods").
		Name(basicInfo.Name).Namespace(basicInfo.Namespace).SubResource("exec").VersionedParams(
		&v1.PodExecOptions{
			Container: basicInfo.Container,
			Command:   []string{basicInfo.DefaultCommand},
			Stdin:     true,
			Stdout:    true,
			Stderr:    true,
			TTY:       true,
		}, scheme.ParameterCodec)
	// 2、创建一个命令执行器SPDY，用于执行K8s命令
	// 创建一个rest config
	// 获取kubeconfig

	logs.Info(nil, "执行到这里了")
	kubeconfig := config.ClusterKubeconfig[basicInfo.ClusterId]
	restConfig, _ := clientcmd.RESTConfigFromKubeConfig([]byte(kubeconfig))
	executor, err := remotecommand.NewSPDYExecutor(restConfig, "POST", execRequest.URL())
	if err != nil {
		logs.Error(map[string]interface{}{"msg": err.Error()}, "创建命令执行器失败")
		returnData.Message = "创建命令执行器失败" + err.Error()
		returnData.Status = 400
		r.JSON(200, returnData)
		return
	}

	// 2.1 升级为websocket
	ws := CreateWS(r)
	defer ws.Close()

	// 2.2 定义一个ptyHandler用于实现streamOptions相关的接口
	pty := &ptyHandler{
		ws: ws,
	}
	// 2.3 定义一个StreamOptions
	// 用结构体实现接口，
	streamOptions := remotecommand.StreamOptions{
		Stdin:             pty,
		Stdout:            pty,
		Stderr:            pty,
		Tty:               true,
		TerminalSizeQueue: pty,
	}

	logs.Info(nil, "执行到这里了2")
	// 3. 创建一个长链接进行实时交互
	err = executor.StreamWithContext(context.TODO(), streamOptions)
	if err != nil {
		logs.Error(map[string]interface{}{"msg": err.Error()}, "命令执行器创建长链接失败")
	}

}
